import java.awt.*;
import java.awt.event.*;
import java.io.*;

/* Daniel Cook
 * 
 * CS4751 Assignment #3
 * 
 * In addition to the rendering and transformation methods required by the assignment,
 * I also added some helper methods to the Vertex class, as well as a Mat3 class
 * to deal with matrix functions in the transformations.
 * 
 */

// Main class
public class ModelTeapot extends Frame {
	// Constructor
	public ModelTeapot(String file) {
		super("Utah Teapot");
		try {
			BufferedReader br = new BufferedReader(new FileReader(file));
			String line = br.readLine();
			int num = Integer.parseInt(line);
			Bezier patches[] = new Bezier[num];
			for ( int i=0; i<num; i++ )
				patches[i] = Bezier.loadBezier(br);
			add(new View3DCanvas(patches));
		} catch ( Exception e ) {
			System.out.println(e);
		}
		addWindowListener(new ExitListener());
	}
	class ExitListener extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
	public static void main(String[] args) {
		ModelTeapot window = new ModelTeapot("teapot.bpt");
		window.setSize(400, 400);
		window.setVisible(true);
	}
}

class View3DCanvas extends Canvas {
	// the Bezier surface to display.
	Bezier patches[];
	// pitch and paw angles for world to camera transform
	int pitch, yaw;
	// focal length and image size for camera to screen transform
	int focal, size;
	// canvas size for screen to raster transform
	int width, height, scale;
	// initialize the 3D view canvas
	public View3DCanvas(Bezier p[]) {
		patches = p;
		focal = 5; size = 1;
		DragListener drag = new DragListener();
		addMouseListener(drag);
		addMouseMotionListener(drag);
		addKeyListener(new ArrowListener());
		addComponentListener(new ResizeListener());
	}
	// Added for assignment: transforms world space -> camera space
	public Vertex world2Camera(Vertex from) {
		//X-axis rotation
		Mat3 xrot = new Mat3(1, 0, 0,
							0, Math.cos((double)Math.toRadians(pitch)), -Math.sin((double)Math.toRadians(pitch)),
							0, Math.sin((double)Math.toRadians(pitch)), Math.cos((double)Math.toRadians(pitch)));
							
		//Z-axis rotation
		Mat3 zrot = new Mat3(Math.cos((double)Math.toRadians(yaw)), -Math.sin((double)Math.toRadians(yaw)), 0,
							Math.sin((double)Math.toRadians(yaw)), Math.cos((double)Math.toRadians(yaw)), 0,
							0, 0, 1);
		
		//Composite rotation matrix, used to transform incoming vertex
		Mat3 rot = xrot.mult(zrot);
		Vertex tmp = rot.multVertex(from);
		
		return new Vertex(tmp.x, tmp.z-1.35, tmp.y);
	}
	// Added for assignment: camera space -> screen space via parallel projection
	public Vertex camera2Screen(Vertex from) {
		Mat3 cam = new Mat3(focal, 0, 0,
							 0, focal, 0,
							 0, 0, 1);
		
		Vertex tmp = cam.multVertex(from);
		
		return new Vertex(tmp.x/size, tmp.y/size, tmp.z/size);
	}
	// Transform between screen and raster coordinates
	public Vertex screen2Raster(Vertex from) {
		return new Vertex(from.x*scale+width/2, -from.y*scale+height/2, 0);
	}
	public void paint(Graphics g) {
		// display current parameter values
		g.setColor(Color.blue);
		g.drawString("Pitch = "+pitch+", Yaw = "+yaw+", Focal = "+focal+", Size = "+size, 10, 20);
		// draw the Bezier surface.
		g.setColor(Color.black);
		for ( int i=0; i<patches.length; i++ )
			patches[i].paint(g, this);
	}

	// Resize listener for updating canvas size
	class ResizeListener extends ComponentAdapter {
		public void componentResized(ComponentEvent e) {
			width = getWidth();
			height = getHeight();
			scale = Math.min(width/2, height/2);
		}
	}
	// Action listener for mouse
	class DragListener extends MouseAdapter implements MouseMotionListener {
		int lastX, lastY;
		public void mousePressed(MouseEvent e) {
			lastX = e.getX();
			lastY = e.getY();
		}
		public void mouseMoved(MouseEvent e) {}
		// update pitch and yaw angles when the mouse is dragged.
		public void mouseDragged(MouseEvent e) {
			yaw += e.getX() - lastX;
			pitch += e.getY() - lastY;
			lastX = e.getX();
			lastY = e.getY();
			repaint();
		}
	}
	// Action listener for keyboard
	class ArrowListener extends KeyAdapter {
		public void keyPressed(KeyEvent e) {
			if ( e.getKeyCode() == e.VK_DOWN && focal>5 )
				focal --;
			else if ( e.getKeyCode() == e.VK_UP && focal<50 )
				focal ++;
			else if ( e.getKeyCode() == e.VK_LEFT && size>1 )
				size --;
			else if ( e.getKeyCode() == e.VK_RIGHT && size<20 )
				size ++;
			repaint();
		}
	}
}

// Vertex (3D point) definition
class Vertex {
	double x, y, z;
	// constructors
	public Vertex(double a, double b, double c) {
		x = a; y = b; z = c;
	}
	public Vertex(double u, Vertex[] ctrl){
		Vertex a = ctrl[0].multiplyScalar(Math.pow(1.0-u, 3.0));
		Vertex b = ctrl[1].multiplyScalar(3.0*u*Math.pow(1.0-u, 2.0));
		Vertex c = ctrl[2].multiplyScalar(3.0*Math.pow(u, 2.0)*(1.0-u));
		Vertex d = ctrl[3].multiplyScalar(Math.pow(u, 3.0));
		
		Vertex r = a.add(b).add(c).add(d);
		x = r.x;
		y = r.y;
		z = r.z;
	}
	public Vertex(double u, Vertex c1, Vertex c2, Vertex c3, Vertex c4){
		Vertex a = c1.multiplyScalar(Math.pow(1.0-u, 3.0));
		Vertex b = c2.multiplyScalar(3.0*u*Math.pow(1.0-u, 2.0));
		Vertex c = c3.multiplyScalar(3.0*Math.pow(u, 2.0)*(1.0-u));
		Vertex d = c4.multiplyScalar(Math.pow(u, 3.0));
		
		Vertex r = a.add(b).add(c).add(d);
		x = r.x;
		y = r.y;
		z = r.z;
	}	
	// parse a vertex from a line of string.
	public static Vertex parseVertex(String input) {
		String tokens[] = input.split(" ");
		double x = Double.parseDouble(tokens[0]);
		double y = Double.parseDouble(tokens[1]);
		double z = Double.parseDouble(tokens[2]);
		return new Vertex(x, y, z);
	}
	
	//Add a vertex to this vertex
	public Vertex add(Vertex b){
		return new Vertex(this.x+b.x, this.y+b.y, this.z+b.z);
	}
	
	//Multiply this vertex by a scalar (scale)
	public Vertex multiplyScalar(double s){
		return new Vertex(this.x*s, this.y*s, this.z*s);
	}
	
	//Find the dot product between this vertex and another
	public double dot(Vertex b){
		return ((this.x*b.x) + (this.y*b.y) + (this.z*b.z));
	}
}

// Class representing a 3x3 matrix
class Mat3{
	double[][] m = new double[3][3];
	
	public Mat3(double m00, double m01, double m02,
		 double m10, double m11, double m12,
		 double m20, double m21, double m22){
		
		m[0][0] = m00;
		m[0][1] = m01;
		m[0][2] = m02;
		m[1][0] = m10;
		m[1][1] = m11;
		m[1][2] = m12;
		m[2][0] = m20;
		m[2][1] = m21;
		m[2][2] = m22;
	}
	
	//Multiply a matrix by a vertex
	public Vertex multVertex(Vertex v){
		Vertex r = new Vertex(-1,-1,-1);
		
		Vertex a = new Vertex(m[0][0], m[0][1], m[0][2]);
		Vertex b = new Vertex(m[1][0], m[1][1], m[1][2]);
		Vertex c = new Vertex(m[2][0], m[2][1], m[2][2]);
		
		r.x = a.dot(v);
		r.y = b.dot(v);
		r.z = c.dot(v);
		
		return r;
	}
	
	//Multiply this matrix by another matrix
	public Mat3 mult(Mat3 n){
		
		Vertex a = new Vertex(m[0][0], m[0][1], m[0][2]);
		Vertex b = new Vertex(m[1][0], m[1][1], m[1][2]);
		Vertex c = new Vertex(m[2][0], m[2][1], m[2][2]);
		
		Vertex a1 = new Vertex(n.m[0][0], n.m[1][0], n.m[2][0]);
		Vertex b1 = new Vertex(n.m[0][1], n.m[1][1], n.m[2][1]);
		Vertex c1 = new Vertex(n.m[0][2], n.m[1][2], n.m[2][2]);
		
		Mat3 r = new Mat3(a.dot(a1), a.dot(b1), a.dot(c1),
							b.dot(a1), b.dot(b1), b.dot(c1),
							c.dot(a1), c.dot(b1), c.dot(c1));
							
		return r;
	}
}

//Utility class for sampling a point on a Bezier surface
class SurfaceSample{
	
	public Vertex point;
	
	public SurfaceSample(double u, double v, Vertex controls[][]){
		point = new Vertex(v, new Vertex(u, controls[0][0], controls[1][0], controls[2][0], controls[3][0]),
							    new Vertex(u, controls[0][1], controls[1][1], controls[2][1], controls[3][1]),
							    new Vertex(u, controls[0][2], controls[1][2], controls[2][2], controls[3][2]),
							    new Vertex(u, controls[0][3], controls[1][3], controls[2][3], controls[3][3]));
	}
	
}

// Triangles definition
class Bezier {
	// the 4 by 4 control points
	Vertex controls[][] = new Vertex[4][4];
	// load one Bezier surface from file.
	public static Bezier loadBezier(BufferedReader reader) throws IOException {
		Bezier b = new Bezier();
		reader.readLine();
		for ( int i=0; i<4; i++ ) {
			String tokens[] = reader.readLine().split("\t");
			for ( int j=0; j<4; j++ )
				b.controls[i][j] = Vertex.parseVertex(tokens[j]);
		}
		return b;
	}
	public void paint(Graphics g, View3DCanvas view) {		
		double num = 8.0;
		double num_u = 4.0;
		double num_v = 4.0;
		
		double d = 1.0/num;
		double du = 1.0/(num_u - 1.0);
		double dv = 1.0/(num_v - 1.0);
		
		//Bezier patch drawing as per the notes
		for(double u=0.0; u<=1.0; u+=du){
			Vertex p0 = new SurfaceSample(u, 0, controls).point;
			p0 = view.screen2Raster(view.camera2Screen(view.world2Camera(p0)));
			
			for(double v=0.0; v<=1.0; v+=d){
				Vertex p1 = new SurfaceSample(u, v, controls).point;
				p1 = view.screen2Raster(view.camera2Screen(view.world2Camera(p1)));
				
				g.drawLine((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y);
				
				p0 = p1; //This isn't in the notes, but it doesn't work correctly without it
			}
		}
		
		for(double v=0.0; v<=1.0; v+=dv){
			Vertex p0 = new SurfaceSample(0, v, controls).point;
			p0 = view.screen2Raster(view.camera2Screen(view.world2Camera(p0)));
			
			for(double u=0.0; u<=1.0; u+=d){
				Vertex p1 = new SurfaceSample(u, v, controls).point;
				p1 = view.screen2Raster(view.camera2Screen(view.world2Camera(p1)));
				
				g.drawLine((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y);
				
				p0 = p1;
			}
		}
	}
}
